home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / WAIS / ir / irsearch.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-02-03  |  13.8 KB  |  480 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.    
  4.    Brewster@think.com
  5. */
  6.  
  7. /* Looks up words in the inverted file index.
  8.  * Please pardon my novice C code.
  9.  *
  10.  * -brewster
  11.  */
  12.  
  13. /* Important functions:
  14.  * run_search
  15.  * search_for_words
  16.  */
  17.  
  18. /* to do:
  19.  *    handle the null request by answering something.
  20.  *    answer questions that are just "help" and "?"
  21.  *    Handle searches on multiple databases
  22.  */
  23.  
  24. /* changes 5.2.90 HWM
  25.     - changed calls to perror() to calls to panic()
  26.     - made print_best_hits() only print hits w/ non-zero weight
  27.     - made random arrays static instead of reading them in.  
  28.       removed getRandomArray.
  29.     - removed unused variables
  30.   Brewster 7/90 made look_up_word_in_dictionary safer.
  31.   Brewster 7/90 elimiated trailing <lf> on filename and headline table accesses
  32.   HWM 7.12.90 - replaced all calls to panic with error code returns and a log
  33.                 file  
  34.           - added the routine initSearchEngine() which should be called 
  35.             before any other search routine
  36.           - added beFriendly() to give other processes time under 
  37.             multifinder
  38.   JG 5.31.91 - added relevance feedback for line fragments.
  39.   JG 7.8.91  - added doc_id to search_for_words, removed scale_scores.
  40. */
  41.  
  42. #define _search_c
  43.  
  44. #include <ctype.h>
  45.  
  46. #include <string.h>     /* for strlen() */
  47. #ifdef THINK_C
  48. #include <unix.h>         /* for sleep() */
  49. #endif /* think_c */
  50.  
  51. #include "cutil.h"
  52. #include "irfiles.h"
  53. #include "irlex.h"
  54. #include "irext.h"
  55. #include "irsearch.h"
  56. #include "docid.h"
  57. #include <math.h>
  58.  
  59. #define TEST_SEARCH     false    /* set to TRUE to allow printing to console */
  60.  
  61. /*----------------------------------------------------------------------*/
  62.  
  63. static Boolean calcDocLength _AP((hit* theHit,long* lines,long* bytes));
  64.  
  65. static Boolean
  66. calcDocLength(theHit,lines,bytes)
  67. hit* theHit;
  68. long* lines;
  69. long* bytes;
  70. /* Given a hit, open the file and figure out how many bytes and lines
  71.    it contains.  This is not needed by the serial search engine (it
  72.    stores these values in its dictionary.  It is used by the dynamic
  73.    help facility).
  74. */
  75. {
  76.   *lines = theHit->number_of_lines;
  77.  
  78.   /* find the length of the document */
  79.   if(theHit->end_character != 0)
  80.     {
  81.       /* document is not whole file, so size is stored */
  82.       *bytes = theHit->end_character - theHit->start_character;
  83.       return(true);
  84.     }
  85.   else
  86.     {    
  87.       /* whole file, find file length from the file */
  88.       FILE* file = NULL;
  89.       if (((file = s_fopen(theHit->filename, "r")) != NULL) &&
  90.       (s_fseek(file, 0L, SEEK_END) == 0)  &&
  91.       ((*bytes = ftell(file)) != -1))
  92.     { s_fclose(file);
  93.       return(true);        /* we are done, bytes is set */
  94.     }
  95.       else
  96.     { s_fclose(file);
  97.       return(false);    /* something went wrong with the file */
  98.     }
  99.     }
  100. }
  101.  
  102.  
  103.  
  104.  
  105. static long wordDelimiter _AP((long c));
  106.  
  107. static long wordDelimiter(c)
  108. long c;
  109. /* decide if c is a delimiter or not */
  110.   if (isalnum((char)(c & 0xFF)))
  111.     return(NOT_DELIMITER);
  112.   else
  113.     return(IS_DELIMITER);
  114. }
  115.  
  116. boolean search_for_words(words, db, doc_id)
  117.      char* words;
  118.      /* break the string into words (delimited by non-alphanumerics) 
  119.     and repeatedly call 
  120.     search_for_word(). Note that the string is modified in the process!
  121.     XXX could do something interesting to return feedback on which of the seedwords
  122.     was most/least important
  123.     Returns true if successful.
  124.     */
  125.      database *db;
  126.      long doc_id;
  127. {
  128.   char* word = NULL;
  129.   /* printf("words: %s\n", words); */
  130.   word = strtokf(words,wordDelimiter);
  131.   while(word != NULL){
  132.     long dictionary_value;
  133.     /* trim the string if necessary */
  134.     if(strlen(word) > MAX_WORD_LENGTH){
  135.       word[MAX_WORD_LENGTH] = '\0';
  136.     }
  137.     dictionary_value = look_up_word_in_dictionary(string_downcase(word), db);
  138.     if(dictionary_value > 0){
  139.       if(0 != search_word(word, 0L, 0L, 1L, doc_id, dictionary_value, db))
  140.     return(false);
  141.     }
  142.     word = strtokf(NULL,NULL);
  143.     beFriendly();
  144.   } 
  145.   return(true);
  146. }
  147.  
  148. /* gets the next best hit from the search engine and fills in all the slots.
  149.    If the document does not exist, then it gets another, etc.
  150.    It returns 0 if successful */   
  151. long next_best_hit(the_best_hit, db)
  152.      hit *the_best_hit;
  153.      database *db;
  154. {
  155.   document_table_entry doc_entry;
  156.   long ret_value;
  157.   while(1){ /* keep going until we get a good document */
  158.     if(0 != (ret_value = best_hit(&(the_best_hit->document_id), &(the_best_hit->weight))))
  159.       return(ret_value);
  160.     if(the_best_hit->weight <= 0)    /* if we are out of good stuff, return */
  161.       return(1);
  162.     /* fill in the rest of the hit */
  163.     if (read_document_table_entry(&doc_entry,
  164.                   the_best_hit->document_id,
  165.                   db) 
  166.     == true){
  167.       the_best_hit->start_character = doc_entry.start_character;
  168.       the_best_hit->end_character = doc_entry.end_character;
  169.       the_best_hit->document_length = doc_entry.document_length;
  170.       the_best_hit->number_of_lines = doc_entry.number_of_lines;
  171.       sprintf(the_best_hit->date, "%d", doc_entry.date);
  172.       read_filename_table_entry(doc_entry.filename_id, 
  173.                 the_best_hit->filename,
  174.                 the_best_hit->type,
  175.                 NULL,
  176.                 db),
  177.       strncpy(the_best_hit->headline, 
  178.           read_headline_table_entry(doc_entry.headline_id,db),
  179.           MAX_HEADLINE_LEN);
  180.       if(probe_file(the_best_hit->filename))
  181.     return(0);  /* we win */
  182.       else /* we lose */
  183.     {
  184.     waislog(WLOG_HIGH, WLOG_ERROR, 
  185.         "Dangling File %s in database %s.", 
  186.         the_best_hit->filename,
  187.         db->database_file);
  188.     /*
  189.     strncpy(the_best_hit->headline, "***Missing Document***: ",
  190.         MAX_HEADLINE_LEN);
  191.     strncat(the_best_hit->headline,
  192.         read_headline_table_entry(doc_entry.headline_id,db),
  193.         MAX_HEADLINE_LEN - strlen(the_best_hit->headline));
  194.     return(0);
  195.     */
  196.       }
  197.     }
  198.     else {
  199.       waislog(WLOG_HIGH, WLOG_ERROR, 
  200.           "Error reading doc_table_entry for database %s, docid: %ld",
  201.           db->database_file,
  202.           the_best_hit->document_id);
  203.     }
  204.     beFriendly();
  205.   }
  206. }
  207.  
  208. /*----------------------------------------------------------------------*/
  209.  
  210. boolean run_search(aSearch, headers, diags, index_directory, 
  211.            seed_words_used, waisProtocolVersion, headerNum)
  212.      SearchAPDU* aSearch;
  213.      WAISDocumentHeader** headers; /* list of results */
  214.      diagnosticRecord*** diags;  /* list of diagnostics */
  215.      char *index_directory;
  216.      char **seed_words_used;  /* called with enough space */
  217.      long waisProtocolVersion;
  218.      long *headerNum;
  219. /* runs a search on the inverted file index and returns false if it errors 
  220.    in such a way that it can not even make a diagnostic record 
  221.    (should not happen).
  222.    It changes headers with the replies or makes a diagnostic record
  223.  */
  224.   diagnosticRecord* diag = NULL;
  225.   WAISSearch* wais_search = (WAISSearch*)aSearch->Query; /* for convenience */
  226.   char* new_db_name = (aSearch->DatabaseNames == NULL) ?
  227.     merge_pathnames(INFO_DATABASE_NAME, index_directory) : 
  228.   merge_pathnames(aSearch->DatabaseNames[0], index_directory);
  229.   char* dbName = new_db_name;
  230.   database* db;
  231.   long maxRawScore;
  232.   long normalScore;
  233.   char* originName = NULL;
  234.   long i;
  235.   query_parameter_type parameters;
  236.   boolean search_result;
  237.  
  238.   db = openDatabase(new_db_name, false, true);
  239.   if (db == NULL)
  240.     { char msg[MAX_FILENAME_LEN * 2];
  241.       strncpy(msg,"The following database is not available: ",
  242.           MAX_FILENAME_LEN);
  243.       s_strncat(msg,new_db_name,MAX_FILENAME_LEN,MAX_FILENAME_LEN);
  244.       diag = makeDiag(false,D_PermanentSystemError,msg);
  245.       *diags = (diagnosticRecord **)s_realloc(*diags,(size_t)(sizeof(diagnosticRecord*) * 2));
  246.       (*diags)[0] = diag;
  247.       (*diags)[1] = NULL;
  248.       return(false);
  249.     }
  250.  
  251.   {
  252.     DocObj** docs = NULL;
  253.  
  254.     /* read the query */
  255.     docs = wais_search->Docs;
  256.     if(docs != NULL) {
  257.       if(docs[0] != NULL && docs[0]->Type != NULL) {
  258.     long id = -1;
  259.     if(strcmp(docs[0]->Type,"WAIS_NEXT") == 0)
  260.       id = next_docid(anyToString(GetLocalID(docIDFromAny(docs[0]->DocumentID))),
  261.               db);
  262.     else if(strcmp(docs[0]->Type,"WAIS_PREV") == 0)
  263.       id = previous_docid(anyToString(GetLocalID(docIDFromAny(docs[0]->DocumentID))),
  264.                   db);
  265.     if (id > -1) {
  266.       document_table_entry doc_entry;
  267.       hit foo;
  268.       long lines,length;
  269.       DocID* theDocID = NULL;
  270.       char local_id[MAX_FILENAME_LEN + 60]; /* filename, start, end */
  271.       local_id[0] = '\0';
  272.  
  273.       if (read_document_table_entry(&doc_entry, id, db) == true) {
  274.         foo.start_character = doc_entry.start_character;
  275.         foo.end_character = doc_entry.end_character;
  276.         foo.document_length = doc_entry.document_length;
  277.         foo.number_of_lines = doc_entry.number_of_lines;
  278.  
  279.         read_filename_table_entry(doc_entry.filename_id, 
  280.                       foo.filename,
  281.                       foo.type,
  282.                       NULL,
  283.                       db),
  284.         strncpy(foo.headline, 
  285.             read_headline_table_entry(doc_entry.headline_id,db),
  286.             MAX_HEADLINE_LEN);
  287.         sprintf(foo.date, "%d", doc_entry.date);
  288.         sprintf(local_id, "%ld %ld %s", 
  289.             doc_entry.start_character,
  290.             doc_entry.end_character,
  291.             foo.filename);
  292.         
  293.         if (calcDocLength(&(foo),&lines,&length))
  294.           {            /* this document is good, return it */
  295.         char** type = NULL;
  296.         
  297.         if (waisProtocolVersion >= '2')
  298.           { type = (char**)s_malloc((size_t)(sizeof(char*) * 2));
  299.             type[0] = s_strdup(foo.type);
  300.             type[1] = NULL;
  301.           }
  302.         else
  303.           type = NULL;
  304.  
  305.         theDocID = makeDocID();
  306.         theDocID->originalDatabase = stringToAny(dbName);
  307.         theDocID->originalLocalID = stringToAny(local_id);
  308.         headers[(*headerNum)++] = 
  309.           makeWAISDocumentHeader(anyFromDocID(theDocID),
  310.                      UNUSED,
  311.                      -1L,
  312.                      UNUSED,length,lines,
  313.                      type,
  314.                      s_strdup(dbName),
  315.                      s_strdup(foo.date),
  316.                      s_strdup(foo.headline),
  317.                      NULL);
  318.         headers[*headerNum] = NULL;
  319.         freeDocID(theDocID);
  320.         return(true);
  321.           }
  322.         else
  323.           { 
  324.         waislog(WLOG_HIGH, WLOG_ERROR, 
  325.             "document <%ld %ld %s> skipped.",
  326.             doc_entry.start_character,
  327.             doc_entry.end_character,
  328.             foo.filename);
  329.         return(true);
  330.           }
  331.       }
  332.           
  333.     }
  334.       }
  335.     }    
  336.   }
  337.   /* until seed_words_used is supported */
  338.   strcpy(*seed_words_used, wais_search->SeedWords);
  339.  
  340.   /* note that the serial search engine does not do relevance feedback.
  341.      As such, fed back doc-id's are ignored.  In a real system, we might
  342.      want to generate diagnostics if such an id was inappropriate for this
  343.      database (of course the UI should intercept such requests in the first
  344.      place - but...It has no way of knowing what a server can handle!)
  345.      */
  346.  
  347.   parameters.max_hit_retrieved = wais_search->MaxDocumentsRetrieved;
  348.   set_query_parameter(SET_MAX_RETRIEVED_MASK, ¶meters);
  349.  
  350.   search_result = false;
  351.  
  352.  
  353. #ifdef RELEVANCE_FEEDBACK
  354. #define MAX_TEXT_SIZE 10000    /* Maximume size of relevant text */
  355.   {
  356.     WAISDocumentText *doctext, *getData(), *getDocumentText();
  357.     DocObj** docs = NULL;
  358.     DocObj* doc = NULL;
  359.  
  360.     /* read the query */
  361.     docs = wais_search->Docs;
  362.     if(docs != NULL) {
  363.       /* assemble the elements and construct a response */
  364.       for (i = 0, doc = docs[i]; doc != NULL; doc = docs[++i])
  365.     {
  366.       if(doc->Type == NULL ||
  367.          strcmp(doc->Type,"TEXT") == 0 ||
  368.          doc->Type[0] == 0) {
  369.  
  370.         long errorCode;
  371.         doctext = NULL;
  372.  
  373.         if (doc->ChunkCode == CT_line)
  374.           doctext = getDocumentText(doc, dbName, &errorCode);
  375.         else if ((doc->ChunkCode == CT_byte) ||
  376.              (doc->ChunkCode == CT_document))
  377.           doctext = getData(doc, dbName, &errorCode);
  378.         if (doctext != NULL) {
  379.           if(doctext->DocumentText->size > MAX_TEXT_SIZE)
  380.         doctext->DocumentText->bytes[MAX_TEXT_SIZE] = 0;
  381.           search_result |= 
  382.         search_for_words(doctext->DocumentText->bytes, db, 1);
  383.           freeWAISDocumentText(doctext);
  384.         }
  385.       }
  386.     }
  387.     }
  388.   }
  389. #endif                /* RELEVANT_FEEDBACK */
  390.  
  391.   search_result |= search_for_words(wais_search->SeedWords, db, 0);
  392.  
  393.   if (search_result == true)
  394.     {                /* the search went ok */
  395.       hit best_hit;
  396.       originName = dbName;
  397.  
  398.       finished_search_word(db);
  399.       for (i = 0; i < wais_search->MaxDocumentsRetrieved; i++){ 
  400.     if(0 != next_best_hit(&best_hit, db))
  401.       break;        /* out of hits */
  402.     if(i == 0)
  403.       maxRawScore = best_hit.weight;
  404.     if (best_hit.weight > 0){
  405.       long lines,length;
  406.       DocID* theDocID = NULL;
  407.       char local_id[MAX_FILENAME_LEN + 60]; /* filename, start, end */
  408.       local_id[0] = '\0';
  409.  
  410.       if (calcDocLength(&(best_hit),&lines,&length))
  411.         {            /* this document is good, return it */
  412.           char** type = NULL;
  413.           normalScore = (long)floor(
  414.                     (((double)best_hit.weight) /
  415.                      ((double)maxRawScore)) *    
  416.                     (MAX_NORMAL_SCORE + 1));
  417.           if (normalScore > MAX_NORMAL_SCORE)
  418.         normalScore = MAX_NORMAL_SCORE;
  419.  
  420.           sprintf(local_id, "%ld %ld %s", 
  421.               best_hit.start_character,
  422.               best_hit.end_character,
  423.               best_hit.filename);
  424.          
  425.           if (waisProtocolVersion >= '2')
  426.         { type = (char**)s_malloc((size_t)(sizeof(char*) * 2));
  427.           type[0] = s_strdup(best_hit.type);
  428.           type[1] = NULL;
  429.         }
  430.           else
  431.         type = NULL;
  432.           /*
  433.         printf("header %ld out of %ld\n", *headerNum, 
  434.         wais_search->MaxDocumentsRetrieved); 
  435.         */
  436.           theDocID = makeDocID();
  437.           theDocID->originalDatabase = stringToAny(originName);
  438.           theDocID->originalLocalID = stringToAny(local_id);
  439.           headers[(*headerNum)++] = 
  440.         makeWAISDocumentHeader(anyFromDocID(theDocID),
  441.                        UNUSED,
  442.                        (long)normalScore,
  443.                        UNUSED,length,lines,
  444.                        type,
  445.                        s_strdup(originName),
  446.                        s_strdup(best_hit.date),
  447.                        s_strdup(best_hit.
  448.                         headline),
  449.                        NULL);
  450.           headers[*headerNum] = NULL;
  451.           freeDocID(theDocID);
  452.         }
  453.       else
  454.         { 
  455.           waislog(WLOG_HIGH, WLOG_ERROR, 
  456.               "document <%ld %ld %s> skipped.",
  457.               best_hit.start_character,
  458.               best_hit.end_character,
  459.               best_hit.filename);
  460.           return(true);
  461.         }
  462.     }
  463.       }
  464.     }
  465.   else
  466.     {                /* something went awry in the search */
  467.       diag = makeDiag(true,D_PermanentSystemError,
  468.               "Serious error in server");
  469.       *diags = (diagnosticRecord**)s_realloc(*diags,(size_t)(sizeof(diagnosticRecord*) * 2));
  470.       (*diags)[0] = diag;
  471.       (*diags)[1] = NULL;
  472.     }
  473.   finished_best_hit();
  474.   /* free everything */
  475.   closeDatabase(db);
  476.   return(true);
  477. }
  478.